package com.cskaoyan.javase.object._3equals;

/**
 * equals方法:
 * 1.方法的声明: public boolean equals(Object obj)
 *      方法的访问权限是public,不需要考虑权限问题
 *      方法返回值是布尔类型
 *      并且该方法需要传参,而且由于方法的参数类型是Object
 *          这意味着,该方法可以传入任何类型的对象
 *
 * 2.方法的作用:
 *      equals有相等的意思,而且这个方法的返回值是布尔值
 *      所以这个方法的作用是判断两个对象是否相等
 *          (方法的调用者对象和方法的传入的参数对象)
 *
 * 3.方法判断相等的规则:
 *      方法的实现(方法体)表示判断相等的规则
 *      a.Object类当中默认实现的相等规则:
 *          return (this == obj);
 *      用"=="判断相等,是比较对象的地址,换句话说,只有当两个引用所指向的对象
 *          是同一个对象时,对象才相等.
 *      但是这个相等的规则有点过于苛刻的,所以一般我们都会重写该方法
 *
 *      b.我们认定的对象相等:
 *           1.如果对象的类型不同,没有可比性,直接就是不相等
 *           2.如果对象的类型相同,那么它们的行为是一致的,这时只要属性的取值一样
 *              那么就可以认为对象相等.
 *
 *      c.重写equals方法,必须遵守以下规则(equals方法重写的常规协定):
 *          1.自反性：对于任何非空引用值 x，x.equals(x)都应返回 true
 *          自己和自己比,是一定相等的
 *
 *          2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *          排除非同种类型的对象和null
 *
 *          3.对称性：对于任何非空引用值 x 和 y，当且仅当y.equals(x) 返回 true 时，x.equals(y) 才应返回true
 *          a = b -> b = a
 *
 *          4.传递性：对于任何非空引用值 x、y 和 z，如果x.equals(y)返回 true，并且 y.equals(z) 返回 true，那么x.equals(z) 应返回 true。
 *          a = b
 *          b = c
 *          ->
 *          a = c
 *
 *          5.一致性：对于任何非空引用值 x 和 y，多次调用 x.equals(y)始终返回 true 或始终返回 false。(用于判断相等的条件没有改变)
 *
 *
 * 注意事项(细节问题):
 *      1.equals方法一般不需要手写,而是可以用快捷键alt+insert自动生成.
 *
 *      2.浮点数的大小比较,在一般情况下,浮点数比较大小用"=="完全没有问题.
 *      但是浮点数存在无穷大,无穷小,非数(NaN)等等特殊的概念,所以为了更好的比较浮点数的大小,浮点数的包装类型中提供了compare方法,用于比较浮点数大小
 *    int Float.compare(float1,float2)
 *    int Double.compare(double1,double2)
 * 这两个方法返回值都是int,这时我们可以想象这个方法是
 * float1 - float2,double1 - double2
 * 当方法返回负数时,说明float1小,float2大
 * 当方法返回正数时,说明float1大,float2小
 * 当返回返回0时,说明float1和float2相等
 *
 *      3.在实现排它性,有两种选择:
 *          a.使用getClass,这种排它性很严格,只有完全是相同类型,才会不返回false
 *          b,使用instanceof,这种排它性没有那么严格了,如果是该类的子类类型,不会返回false.
 *
 *      4.如果类中还有引用数据类型成员变量,那么在equals方法中继续调用它的equals方法
 *          String类当中的equals方法是遍历value字符数组,然后逐一比对
 *          只有当value数组的内容,即字符串对象的内容完全一致时
 *          会返回true
 *          字符串内容不一致就会返回false
 *
 *       5.补充:
 *          浮点数存在精度问题,需要确保精度的场合,不应该使用浮点数
 *          推荐使用BigDecimal这个类做十进制小数的运算
 *          BigDecimal表示一个任意精度的十进制数
 *
 *
 * @since 10:56
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));

        Teacher t = new Teacher(18, 100);
        System.out.println(s1.equals(t));

        StudentSon studentSon = new StudentSon(18, 100);
        System.out.println(s1.equals(studentSon));
        System.out.println("-----------------");
        Student s3 = new Student(18, 100, "张三");
        Student s4 = new Student(18, 100, "张三");
        System.out.println(s3.equals(s4));
    }
}

class Student {
    int age;
    double score;
    String name;

    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    public Student(int age, double score, String name) {
        this.age = age;
        this.score = score;
        this.name = name;
    }

    /*@Override
    public boolean equals(Object obj) {
        // 1.自反性：对于任何非空引用值 x，x.equals(x)都应返回 true
        if (this == obj) {
            return true;
        }
        // 代码运行到这里,一定不是同一个对象
        // 2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        // 代码运行到这里,说明obj肯定不是null,而且肯定是Student类型
        *//*
            只要用对象的属性取值判断对象相等
            那么就自动满足:
                1.对称性
                2.传递性
                3.一致性
         *//*
        Student target = (Student) obj;
        if (this.age != target.age) {
            return false;
        }
        return this.score == target.score;
    }*/

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        // 这里的排它性,很严格,只要不是同种类型,都返回false
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}

class StudentSon extends Student {
    int num = 10;
    public StudentSon() {
    }

    public StudentSon(int age, double score) {
        super(age, score);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof StudentSon)) return false;
        if (!super.equals(o)) return false;

        StudentSon that = (StudentSon) o;

        return num == that.num;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + num;
        return result;
    }
}

class Teacher {
    int age;
    double score;

    public Teacher() {
    }

    public Teacher(int age, double score) {
        this.age = age;
        this.score = score;
    }
}